/********************************************************************
Crytek Source File.
Copyright (C), Crytek Studios, 2001-2009.
---------------------------------------------------------------------
File name:   AINetworkDebugRenderer.cpp
$Id$
Description: Helper functions to draw some interesting debug shapes
             over the network.

---------------------------------------------------------------------
History:
- 2009-6-10   : Created by Evgeny Adamenkov

*********************************************************************/

#include "StdAfx.h"

#include "AINetworkDebugRenderer.h"

#include "CryAction.h"
#include "IActorSystem.h"
#include "TimeValue.h"


CAINetworkDebugRenderer::CAINetworkDebugRenderer()
{
	assert(gEnv->IsDedicated());
	SwitchSenderStateTo(eSS_WAITING_FOR_FRAME_START);
}


//////////////////////////////////////////////////////////////////////////
//
//	IAIDebugRenderer interface methods

Vec3 CAINetworkDebugRenderer::GetCameraPos()
{
	static ICVar* cvar_ai_NetworkDebugChannel = gEnv->pConsole->GetCVar("ai_NetworkDebugChannel");

	if (int channel = cvar_ai_NetworkDebugChannel->GetIVal())
	{
		if (IActor* pActor = CCryAction::GetCryAction()->GetIActorSystem()->GetActorByChannelId(channel))
		{
			if (IEntity* pEntity = pActor->GetEntity())
			{
				return pEntity->GetPos();
			}
		}
	}

	return gEnv->pSystem->GetViewCamera().GetPosition();
}

void CAINetworkDebugRenderer::DrawAABB(const AABB& aabb, bool bSolid, const ColorB& color, EBoundingBoxDrawStyle bbDrawStyle)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawAABB << aabb << bSolid << color << bbDrawStyle;
	}
}

void CAINetworkDebugRenderer::DrawAABB(const AABB& aabb, const Matrix34& matWorld, bool bSolid, const ColorB& color, EBoundingBoxDrawStyle bbDrawStyle)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawAABB_matrix << aabb << matWorld << bSolid << color << bbDrawStyle;
	}
}

void CAINetworkDebugRenderer::DrawArrow(const Vec3& vPos, const Vec3& vLength, float fWidth, const ColorB& color)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawArrow << vPos << vLength << fWidth << color;
	}
}

void CAINetworkDebugRenderer::DrawCapsuleOutline(const Vec3& vPos0, const Vec3& vPos1, float fRadius, const ColorB& color)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawCapsuleOutline << vPos0 << vPos1 << fRadius << color;
	}
}

void CAINetworkDebugRenderer::DrawCircleOutline(const Vec3& vPos, float fRadius, const ColorB& color)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawCircleOutline << vPos << fRadius << color;
	}
}

void CAINetworkDebugRenderer::DrawCircles(const Vec3& vPos, float fMinRadius, float fMaxRadius, int numRings, const ColorB& insideColor, const ColorB& outsideColor)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawCircles << vPos << fMinRadius << fMaxRadius << numRings << insideColor << outsideColor;
	}
}

void CAINetworkDebugRenderer::DrawCone(const Vec3& vPos, const Vec3& vDir, float fRadius, float fHeight, const ColorB& color, bool bDrawShaded /* = true */)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawCone << vPos << vDir << fRadius << fHeight << color << bDrawShaded;
	}
}

void CAINetworkDebugRenderer::DrawCylinder(const Vec3& vPos, const Vec3& vDir, float fRadius, float fHeight, const ColorB& color, bool bDrawShaded /* = true */)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawCylinder << vPos << vDir << fRadius << fHeight << color << bDrawShaded;
	}
}

void CAINetworkDebugRenderer::DrawEllipseOutline(const Vec3& vPos, float fRadiusX, float fRadiusY, float fOrientation, const ColorB& color)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawEllipseOutline << vPos << fRadiusX << fRadiusY << fOrientation << color;
	}
}

void CAINetworkDebugRenderer::Draw2dImage(float fX, float fY, float fWidth, float fHeight, int nTextureID, float fS0 /* = 0.f */, float fT0 /* = 0.f */, float fS1 /* = 1.f */, float fT1 /* = 1.f */, float fAngle /* = 0.f */, float fR /* = 1.f */, float fG /* = 1.f */, float fB /* = 1.f */, float fA /* = 1.f */, float fZ /* = 1.f */)
{
	if (IsSenderReady())
	{
		m_outputStream << eDraw2dImage << fX << fY << fWidth << fHeight << nTextureID << fS0 << fT0 << fS1 << fT1 << fAngle << fR << fG << fB << fA << fZ;
	}
}

void CAINetworkDebugRenderer::Draw2dLabel(int nCol, int nRow, const char* szText, const ColorB& color)
{
	if (IsSenderReady())
	{
		m_outputStream << eDraw2dLabelSimple << nCol << nRow << szText << color;
	}
}


#define PREPARE_TEXT(format, buffer)     \
	char buffer[1024]; \
	va_list args; \
	va_start(args, format); \
	if (vsnprintf(buffer, sizeof buffer, format, args) == -1) \
		buffer[sizeof buffer - 1] = 0; \
	va_end(args)

void CAINetworkDebugRenderer::Draw2dLabel(float fX, float fY, float fFontSize, const ColorB& color, bool bCenter, const char* format, ...)
{
	if (IsSenderReady())
	{
		PREPARE_TEXT(format, buffer);
		m_outputStream << eDraw2dLabel << fX << fY << fFontSize << color << bCenter << static_cast<const char*>(buffer);
	}
}

void CAINetworkDebugRenderer::Draw3dLabel(const Vec3& vPos, float fFontSize, const char* format, ...)
{
	if (IsSenderReady())
	{
		PREPARE_TEXT(format, buffer);
		m_outputStream << eDraw3dLabel << vPos << fFontSize << static_cast<const char*>(buffer);
	}
}

void CAINetworkDebugRenderer::Draw3dLabelEx(const Vec3& vPos, float fFontSize, const ColorB& color, bool bFixedSize, bool bCenter, const char* format, ...)
{
	if (IsSenderReady())
	{
		PREPARE_TEXT(format, buffer);
		m_outputStream << eDraw3dLabelEx << vPos << fFontSize << color << bFixedSize << bCenter << static_cast<const char*>(buffer);
	}
}

void CAINetworkDebugRenderer::DrawLine(const Vec3& v0, const ColorB& colorV0, const Vec3& v1, const ColorB& colorV1, float fThickness /* = 1.f */)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawLine << v0 << colorV0 << v1 << colorV1 << fThickness;
	}
}

void CAINetworkDebugRenderer::DrawOBB(const OBB& obb, const Vec3& vPos, bool bSolid, const ColorB& color, EBoundingBoxDrawStyle bbDrawStyle)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawOBB_vector << obb << vPos << bSolid << color << bbDrawStyle;
	}
}

void CAINetworkDebugRenderer::DrawOBB(const OBB& obb, const Matrix34& matWorld, bool bSolid, const ColorB& color, EBoundingBoxDrawStyle bbDrawStyle)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawOBB_matrix << obb << matWorld << bSolid << color << bbDrawStyle;
	}
}

void CAINetworkDebugRenderer::DrawPolyline(const Vec3* va, uint32 nPoints, bool bClosed, const ColorB& color, float fThickness /* = 1.f */)
{
	if (IsSenderReady())
	{
		// nPoints should go before va
		m_outputStream << eDrawPolyline_single_color << nPoints;
		m_outputStream.Write(va, nPoints);
		m_outputStream << bClosed << color << fThickness;
	}
}

void CAINetworkDebugRenderer::DrawPolyline(const Vec3* va, uint32 nPoints, bool bClosed, const ColorB* colorArray, float fThickness /* = 1.f */)
{
	if (IsSenderReady())
	{
		// nPoints should go before va
		m_outputStream << eDrawPolyline_multiple_colors << nPoints;
		m_outputStream.Write(va, nPoints);
		m_outputStream << bClosed;
		m_outputStream.Write(colorArray, nPoints);
		m_outputStream << fThickness;
	}
}

void CAINetworkDebugRenderer::DrawRangeArc(const Vec3& vPos, const Vec3& vDir, float fAngle, float fRadius, float fWidth, const ColorB& colorFill, const ColorB& colorOutline, bool bDrawOutline)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawRangeArc << vPos << vDir << fAngle << fRadius << fWidth << colorFill << colorOutline << bDrawOutline;
	}
}

void CAINetworkDebugRenderer::DrawRangeBox(const Vec3& vPos, const Vec3& vDir, float fSizeX, float fSizeY, float fWidth, const ColorB& colorFill, const ColorB& colorOutline, bool bDrawOutline)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawRangeBox << vPos << vDir << fSizeX << fSizeY << fWidth << colorFill << colorOutline << bDrawOutline;
	}
}

void CAINetworkDebugRenderer::DrawRangeCircle(const Vec3& vPos, float fRadius, float fWidth, const ColorB& colorFill, const ColorB& colorOutline, bool bDrawOutline)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawRangeCircle << vPos << fRadius << fWidth << colorFill << colorOutline << bDrawOutline;
	}
}

void CAINetworkDebugRenderer::DrawRangePolygon(const Vec3* va, uint32 nPoints, float fWidth, const ColorB& colorFill, const ColorB& colorOutline, bool bDrawOutline)
{
	if (IsSenderReady())
	{
		// nPoints should go before va
		m_outputStream << eDrawRangePolygon << nPoints;
		m_outputStream.Write(va, nPoints);
		m_outputStream << fWidth << colorFill << colorOutline << bDrawOutline;
	}
}

void CAINetworkDebugRenderer::DrawSphere(const Vec3& vPos, float fRadius, const ColorB& color, bool bDrawShaded /* = true */)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawSphere << vPos << fRadius << color << bDrawShaded;
	}
}

void CAINetworkDebugRenderer::DrawTriangle(const Vec3& v0, const ColorB& colorV0, const Vec3& v1, const ColorB& colorV1, const Vec3& v2, const ColorB& colorV2)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawTriangle << v0 << colorV0 << v1 << colorV1 << v2 << colorV2;
	}
}

void CAINetworkDebugRenderer::DrawTriangles(const Vec3* va, uint32 numPoints, const ColorB& color)
{
	if (IsSenderReady())
	{
		// numPoints should go before va
		m_outputStream << eDrawTriangles << numPoints;
		m_outputStream.Write(va, numPoints);
		m_outputStream << color;
	}
}

void CAINetworkDebugRenderer::DrawWireFOVCone(const Vec3& vPos, const Vec3& vDir, float fRadius, float fFOV, const ColorB& color)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawWireFOVCone << vPos << vDir << fRadius << fFOV << color;
	}
}

void CAINetworkDebugRenderer::DrawWireSphere(const Vec3& vPos, float fRadius, const ColorB& color)
{
	if (IsSenderReady())
	{
		m_outputStream << eDrawWireSphere << vPos << fRadius << color;
	}
}

ITexture* CAINetworkDebugRenderer::LoadTexture(const char* szNameOfTexture, uint32 nFlags)
{
	ITexture* pTexture = gEnv->pRenderer->EF_LoadTexture(szNameOfTexture, nFlags);
	if (pTexture)
	{
		if (IsSenderReady())
		{
			// The visual renderer needs to know the sender's pTexture->GetTextureID() to draw the corresponding texture
			m_outputStream << eLoadTexture << szNameOfTexture << nFlags << pTexture->GetTextureID();
		}
	}
	return pTexture;
}

void CAINetworkDebugRenderer::TextToScreen(float fX, float fY, const char* format, ...)
{
	if (IsSenderReady())
	{
		PREPARE_TEXT(format, buffer);
		m_outputStream << eTextToScreen << fX << fY << static_cast<const char*>(buffer);
	}
}

void CAINetworkDebugRenderer::TextToScreenColor(int nX, int nY, float fRed, float fGreen, float fBlue, float fAlpha, const char* format, ...)
{
	if (IsSenderReady())
	{
		PREPARE_TEXT(format, buffer);
		m_outputStream << eTextToScreenColor << nX << nY << fRed << fGreen << fBlue << fAlpha << static_cast<const char*>(buffer);
	}
}

bool CAINetworkDebugRenderer::ProjectToScreen(float fInX, float fInY, float fInZ, float* pfOutX, float* pfOutY, float* pfOutZ)
{
	if (IsSenderReady())
	{
		m_outputStream << eProjectToScreen << fInX << fInY << fInZ;
	}

	// Just return some fake numbers, which will ultimately become (fInX, fInY) in the caller
	*pfOutX = 100.f * fInX / GetWidth();
	*pfOutY = 100.f * fInY / GetHeight();
	*pfOutZ = .5f * sgn(fInZ);

	return true;
}

void CAINetworkDebugRenderer::SetMaterialColor(float fRed, float fGreen, float fBlue, float fAlpha)
{
	if (IsSenderReady())
	{
		m_outputStream << eSetMaterialColor << fRed << fGreen << fBlue << fAlpha;
	}
}

void CAINetworkDebugRenderer::SetFlags(SAuxGeomRenderFlags flags)
{
	if (IsSenderReady())
	{
		CAIAbstractDebugRenderer::SetFlags(flags);	// Update the flag stack
		m_outputStream << eSetFlags << flags.m_renderFlags;
	}
}

int CAINetworkDebugRenderer::GetFrameID()
{
	// Somehow we have to know which renderer frame it is, even on dedicated server
	return gEnv->pRenderer->GetFrameID();
}


//////////////////////////////////////////////////////////////////////////
//
//	Sender state machine

bool CAINetworkDebugRenderer::IsSenderReady()
{
	switch (m_eSenderState)
	{
		case eSS_WAITING_FOR_FRAME_START:
		{
			if (GetFrameID() == m_currentFrameID)
				return false;

			SwitchSenderStateTo(eSS_COLLECTING_COMMANDS_TO_SEND);
			return true;
		}
		case eSS_COLLECTING_COMMANDS_TO_SEND:
		{
			if (GetFrameID() == m_currentFrameID)
				return true;

			SwitchSenderStateTo(eSS_IN_DELAY);

			//static FILE* file = fopen("e:\\NetworkDebug.log", "wt");
			//float start = gEnv->pTimer->GetCurrTime();

			m_outputStream.Send();
			
			//float end = gEnv->pTimer->GetCurrTime();
			//double time = end - start;
			//fprintf(file, "Sent %u bytes in %f seconds\n", m_outputStream.Size(), time);
			
			return false;
		}
		case eSS_IN_DELAY:
		{
			static ICVar* cvar_ai_NetworkDebugBytesPerSecond = gEnv->pConsole->GetCVar("ai_NetworkDebugBytesPerSecond");
			static ICVar* cvar_ai_NetworkDebugMinDelay       = gEnv->pConsole->GetCVar("ai_NetworkDebugMinDelay");
			
			float delay = gEnv->pTimer->GetCurrTime() - m_delayStartTime;
			
			// Send the data not faster than ai_NetworkDebugBytesPerSecond bytes per second
			// but make sure there is at least ai_NetworkDebugMinDelay second delay
			if ((cvar_ai_NetworkDebugBytesPerSecond->GetIVal() * delay >= m_outputStream.Size()) &&
			    (delay >= cvar_ai_NetworkDebugMinDelay->GetFVal()))
			{
				SwitchSenderStateTo(eSS_WAITING_FOR_FRAME_START);
			}
			return false;
		}
	}
	
	assert(!!!"Should not be here!");
	return false;
}

void CAINetworkDebugRenderer::SwitchSenderStateTo(ESenderState eSenderNewState)
{
	switch (eSenderNewState)
	{
		case eSS_WAITING_FOR_FRAME_START:
			m_outputStream.Clear();
			m_currentFrameID = GetFrameID();
			break;

		case eSS_COLLECTING_COMMANDS_TO_SEND:
			assert(m_eSenderState == eSS_WAITING_FOR_FRAME_START);
			m_currentFrameID = GetFrameID();
			break;

		case eSS_IN_DELAY:
			assert(m_eSenderState == eSS_COLLECTING_COMMANDS_TO_SEND);
			m_delayStartTime = gEnv->pTimer->GetCurrTime();
			break;
		
		default:
			assert(!!!"Illegal state!");
			return;
	}
	
	m_eSenderState = eSenderNewState;
}



//////////////////////////////////////////////////////////////////////////
//
//	Serialization helper

void CAINetworkDebugRenderer::CDrawCommandsOutputStream::Send()
{
	assert(!m_buffer.empty());

	if (!m_pNotificationNetworkClient)
	{
		if (INotificationNetwork* pNotificationNetwork = gEnv->pSystem->GetINotificationNetwork())
		{
			m_pNotificationNetworkClient = pNotificationNetwork->Connect(gEnv->pConsole->GetCVar("ai_NetworkDebugClientHost")->GetString(), DEFAULT_PORT);
		}
	}

	if (m_pNotificationNetworkClient)
	{
		m_pNotificationNetworkClient->Send(CAIAbstractDebugRenderer::szNetworkChannelName, &m_buffer[0], m_buffer.size());
	}
}


CAINetworkDebugRenderer::CDrawCommandsOutputStream& CAINetworkDebugRenderer::CDrawCommandsOutputStream::operator<<(const char* sz)
{
	size_t str_len = strlen(sz);
	assert((str_len < 1024) && "String too long");
	size_t old_size = m_buffer.size();
	m_buffer.resize(old_size + str_len + 1);
	strcpy(reinterpret_cast<char*>(&m_buffer[old_size]), sz);
	return *this;
}
